Skip to content

feat: auto-close brackets and quotes#336

Merged
bartekplus merged 7 commits intomasterfrom
claude/exciting-nightingale
Mar 22, 2026
Merged

feat: auto-close brackets and quotes#336
bartekplus merged 7 commits intomasterfrom
claude/exciting-nightingale

Conversation

@bartekplus
Copy link
Owner

Summary

Closes #335

  • Adds AutoBracketCloseRule grammar rule that auto-inserts matching closing brackets/quotes when typing opening ones and positions the cursor between them
  • Supports smart overtype (skip-over): typing a closing char when it's already ahead of the cursor skips over it instead of inserting a duplicate
  • Extends GrammarEdit with cursorOffset for mid-replacement cursor positioning, with full support in edit merging (mergeSequentialGrammarEdits) and context simulation (applyGrammarEditToContext)

Supported pairs: (), [], {}, '', "", ``, <>, `«»`

Smart guards:

  • Symmetric quotes (', ", `): suppressed after word characters (apostrophes/contractions)
  • <: suppressed after word characters (comparison operators)
  • > overtype: suppressed after word characters
  • Auto-close suppressed when matching close char already ahead of cursor

Configuration: Off by default, advanced safety tier, priority 134.

Files changed

  • types.ts — added cursorOffset to GrammarEdit, autoBracketClose to GrammarRuleId
  • AutoBracketCloseRule.ts — new rule implementation
  • GrammarEditSequencing.tscursorOffset support in apply/merge
  • ruleCatalog.ts — catalog entry
  • ruleFactory.ts — rule registration
  • SuggestionTextEditService.ts — cursor positioning for cursorOffset
  • fluenttyperI18n.ts — i18n strings (9 languages)
  • AutoBracketCloseRule.test.ts — 27 tests

Test plan

  • bun run lint passes
  • bun run format:check passes
  • bun run test passes (1521 tests, 27 new)
  • bun run test:e2e passes
  • bun run check:e2e:coverage passes
  • Manual test: enable rule in settings, verify auto-close and overtype in text fields

🤖 Generated with Claude Code

bartekplus and others added 7 commits March 22, 2026 07:15
Add a new grammar rule that automatically inserts matching closing
brackets/quotes when typing opening ones, positions the cursor between
them, and supports smart overtype (skip-over) for closing characters.

Supported pairs: (), [], {}, '', "", ``, <>, «»
Smart guards: apostrophe suppression, word-char guards for < and >

Extends GrammarEdit with cursorOffset for mid-replacement cursor
positioning, with full support in edit merging and context application.

Off by default (advanced tier, priority 134).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
For symmetric quotes (", ', `), the engine's steady-state loop would
oscillate: auto-close → overtype → auto-close → overtype → auto-close,
producing """" (4 chars) instead of "" (2 chars).

Root cause: after auto-close places cursor between quotes ("|"), the
engine re-evaluates and overtype fires because the opening quote looks
like a closing quote. This undoes the auto-close, and the cycle repeats.

Fix: only allow overtype for symmetric quotes when preceded by a word
character (genuine closing-quote context like: hello"|). When preceded
by non-word or start-of-string, block overtype so auto-close sticks.

Adds engine integration tests to verify no oscillation for all quote types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues caused the cursor to land at the end of the replacement
instead of in the middle (e.g., "(|)") on contenteditable editors
like Facebook:

1. Host-handled path: when the host editor (Draft.js, Lexical, etc.)
   handles the beforeInput event and applies the text change itself,
   FluentTyper never called setCaret — cursor was wherever the host
   put it. Now setCaret is called after host-handled edits.

2. DOM fallback path: setCaret was called BEFORE dispatchReplacementInput.
   Host editors handle the input event synchronously and override the
   cursor to end of replacement. Swapped the order so setCaret runs
   after the input event, giving our position the final word.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
React-based editors (Facebook/Lexical, Reddit/Slate) reconcile the
DOM asynchronously via microtasks after handling beforeInput events,
overriding any cursor position set synchronously.

Add a deferred setCaret via requestAnimationFrame that runs after
framework reconciliation completes, ensuring the cursor lands in
the middle of auto-closed pairs on these editors.

Only triggers when cursorOffset is set (bracket-close edits), so
normal grammar edits are unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix two issues with cursor placement after auto-bracket-close on
React-based editors like Facebook (Lexical) and Reddit (Slate):

1. The deferred cursor fix was scheduled AFTER the didMutateDom early
   return. React editors handle beforeinput by calling preventDefault()
   and reconciling async via microtask, so didMutateDom is false at
   check time. Move the fix before the early return with text validation.

2. Selection.modify() from the content script's isolated world doesn't
   trigger native selectionchange events that host editors listen for.
   Use the existing main-world bridge to execute cursor movement in the
   page context, where it triggers proper event propagation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The setCaret/dispatchReplacementInput swap broke Quill predictions on
Firefox. Calling setCaret after dispatching the input event interferes
with Quill's internal selection tracking on Firefox, causing prediction
suggestions to stop appearing.

Revert setCaret to its original position (before dispatchReplacementInput)
and remove the redundant setCaret in the host-handled beforeinput path.
Cursor positioning for auto-bracket-close is now handled entirely by the
deferred main-world bridge, which runs after editor reconciliation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bartekplus bartekplus merged commit 1185bb9 into master Mar 22, 2026
8 checks passed
@bartekplus bartekplus deleted the claude/exciting-nightingale branch March 22, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Close brackets and quotes automatically

1 participant